package main

import (
	"bytes"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	_ "github.com/denisenkom/go-mssqldb"
	"gocv.io/x/gocv"
	"image"
	_ "image"
	"image/color"
	"image/jpeg"
	_ "image/png"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"unsafe"
)
//storange Session
type Session struct {
	AccessToken   string `json:"access_token"`
	RefreshToken  string `json:"refresh_token"`
	SessionKey    string `json:"session_key"`
	SessionSecret string `json:"session_secret"`
	Scope         string `json:"scope"`
	ExpiresIn     int    `json:"expires_in"`
}

//storage the all Result
type Result struct {
	LogId     int64       `json:"log_id"`
	Wrn       int         `json:"words_result_num"`
	Wr        []WordsResult `json:"words_result"`
}
//storage the words in the return Result
type WordsResult struct {
	Location Detail `json:"location"`
	Words string `json:"words"` 
}

type Detail struct {
	Width uint `json:"width"`
	Top	uint	`json:"top"`
	Left uint `json:"left"`
	Height uint `json:"height"`
}
//recived the baidu return message(Main)
type Alldata struct {
	Data Data `json:"data"`
	Errorcode int `json:"error_code"`
	Errormsg string `json:"error_msg"`
	LogId string `json:"log_id"`
}
//(Subordinate)
type Data struct {
	Ret []Ret `json:"ret"`
	TemplateSign string `json:"templateSign"`
	Scores float64 `json:"scores"`
	IsStructured bool `json:"isStructured"`
	ClockwiseAngle float64 `json:"clockwiseAngle"`
	TemplateMatchDegree float64 `json:"templateMatchDegree"`
	LogId string `json:"logId"`
	templateName string `json:"templateName"`
}
type Ret struct {
	Probability Probability `json:"probability"`
	Location Location `json:"location"`
	Wordname string `json:"word_name"`
	Word string `json:"word"`
}
type Probability struct {
	Average float64 `json:"average"`
	Min float64 `json:"min"`
	Variance float64 `json:"float64"`
}
type Location struct {
	Height uint `json:"height"`
	Left uint `json:"left"`
	Top uint `json:"top"`
	Width uint `json:"width"`
}

//Package
type imgTrans struct {
	token string
	imgdata []byte//base64
	clientID string
	clientSecret string
}

func main() {
	//badidu OCR
	clientID:="HMeeHK0vQsdtNqEEXqeFZ7cI"
	clientSecret:="hYtG4ohoIHuRHxjm874KyysZERkLceIo"
	FinImg:=getCorrect("main/OCR/666.jpg")

	//output
	window:= gocv.NewWindow("Finally picture")
	window.ResizeWindow(800,400)
	window.IMShow(FinImg)
	window.WaitKey(0)

	imgdata:=MatTransBase64(FinImg)
	//fetch the photo information
	//imgdata, err := ioutil.ReadFile("main/OCR/1.png")
	i:=Newimg(imgdata,clientID,clientSecret)
	//use GET access_token method
	err := i.getaccessToken()
	//Upload the image information and handle the data next println the result
	res:=i.ImageToText(imgdata,FinImg)
	if res == nil || err!=nil{
		fmt.Println("ImageToText() failed,Please try again", err)
		return
	}
	fmt.Println(res)
//	err=i.StorageData(res)
	check(err)
}


func MatTransBase64(FinImg gocv.Mat) []byte{
	x,err:=FinImg.ToImage()
	check(err)
	//trans img to base64
	//开辟一个新的空buff
	emptyBuff := bytes.NewBuffer(nil)
	//img写入到buff
	jpeg.Encode(emptyBuff, x, nil)
	//开辟存储空间
	dist := make([]byte,500000)
	fmt.Println(len(emptyBuff.Bytes()))
	//buff转成base64
	base64.StdEncoding.Encode(dist, emptyBuff.Bytes())
	//这里要注意，因为申请的固定长度数组，所以没有被填充完的部分需要去掉，否则输出可能出错
	index := bytes.IndexByte(dist, 0)
	//最终结果
	imgdata := dist[0:index]
	return imgdata
}
//Constructor method
func Newimg(imgdata []byte,clientID string,clientSecret string) imgTrans{
	return imgTrans{
		"",
		imgdata,
		clientID,
		clientSecret,
	}
}
//argvs
var debug = flag.Bool("debug", true, "enable debugging")
var password = flag.String("password", "root", "the password")
var port *int = flag.Int("port", 1433, "the port")
var server = flag.String("server", "127.0.0.1", "the server")
var user = flag.String("user", "sa", "the user")
var database = flag.String("database", "YQL", "the name")
//GET access_token
func (i *imgTrans)getaccessToken()  error{
	apiURL := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=%s&client_secret=%s&", i.clientID, i.clientSecret)
	resp, err := http.Get(apiURL)
	if err != nil {
		return  err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil{
		return  err
	}
	var session Session
	err = json.Unmarshal(body, &session)
	if err != nil {
		return err
	}
	i.token =session.AccessToken
	return nil
}
//Transfer photo to text,return Identify data
func (i imgTrans)ImageToText(image []byte,mat gocv.Mat) map[int]map[string]string {
	apiURL := fmt.Sprintf("https://aip.baidubce.com/rest/2.0/ocr/v1/accurate?access_token=%s", i.token)
	param := "image=" + url.QueryEscape(*(*string)(unsafe.Pointer(&image)))+"&detect_direction=true&probability=true"
	resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", strings.NewReader(param))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	//用于存储返回的数据
	var result Result
	err = json.Unmarshal(body,&result)
	if err != nil {
		fmt.Println(err)
	}
	//暂时查看
	fmt.Println(result)
	ModifyPixel(mat,result)

	var tempURL = fmt.Sprintf("https://aip.baidubce.com/rest/2.0/solution/v1/iocr/recognise?access_token=%s", i.token)
LOOP:
	for _,v:=range result.Wr{
		switch v.Words{
		case "上海市东方医院(南院)免疫检验报告单"://1.png
			temID:="5bd52cc436e4886d3524d12bc3e150af"
			column:=7
			res:=BlankColumn7(tempURL,temID,param,column)
			return res
			break LOOP

		case "上海市东方医院检验报告单"://2.jpg
			tempID:="c373b9864410930753ad0524c7b8cd82"
			column:=6
			res:=Tab2Column6(tempURL,tempID,param,column)
			return res
			break LOOP

		case "上海市东方医院(南院)生化检验报告单"://3.png
			tempID:="beb32f3e6b1d32e37659fae9c109e816"
			column:=6
			res:=Tab2Column6(tempURL,tempID,param,column)
			return res
			break LOOP
		}
	}
	return nil
}
//Storage the data into database
func (i imgTrans)StorageData(Identify map[int]map[string]string) error{
	//Create database simple
	db,err:=i.GetDB()
	defer db.Close()
	check(err)
	//Get data from parameter Periodicity
	for i:=1;i<=len(Identify);i++{
		Code:=Identify[i]["代号"]
		TestName:=Identify[i]["项目名"]
		result:=Identify[i]["结果"]
		Data:=Identify[i]["较平均"]
		Description:=Identify[i]["参考区间"]
		Unit:=Identify[i]["单位"]
		stmt, err := db.Prepare("Insert INTO BGtable Values(?,?,?,?,?,?)")
		//stmt, err := db.Prepare("Insert BGtable SET Code=?,TestName=?,result=?,Data=?,Description=?")
		res, err := stmt.Exec(Code,TestName,result,Data,Description,Unit)
		affect, err := res.RowsAffected()
		check(err)
		fmt.Printf("返回 query 操作影响的数据条目数：%d\n\n", affect)
	}
	return nil
}
//Create the Simple to use DataBase
func (i imgTrans)GetDB() (*sql.DB, error) {
	connString := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;port=%d;encrypt=disable", *server, *database, *user, *password, *port)
	db, err := sql.Open("mssql", connString)
	if err != nil {
		log.Fatal("Open connection failed:", err.Error())
		return nil, err
	}
	err = db.Ping()
	if err != nil {
		fmt.Print("PING:%s", err)
		return nil, err
	}
	return db, nil
}

//Handle the photo which has Blank area and Column is 7(Can beeen selected)
func BlankColumn7(tempURL string,temIDv string,param string,column int) map[int]map[string]string{
	temparam := fmt.Sprintf("templateSign=%s&%s",temIDv,param)
	resp, err := http.Post(tempURL, "application/x-www-form-urlencoded", strings.NewReader(temparam))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	//结构体接收百度返回数据
	var alldata Alldata
	//反序列化
	err1:= json.Unmarshal(body, &alldata)
	if err1!= nil {
		fmt.Println(err)
	}
	//detail存每行详细数据
	var detail = make(map[string]string)
	//alldetail存 行号+detail
	var alldetail = make(map[int]map[string]string)
	//取得所有百度返回数据
	var opt = alldata.Data.Ret
	//循环变量
	var i =0
	//行号
	var Rkey int
	for i < len(opt) {
		str:=strings.Split(opt[i].Wordname,"#")
		//当循环次数到达新的一行时，i%7=0
		if i%column == 0 {
			//Rkey即表格中“序”对应值，即行号
			Rkey,err=strconv.Atoi(opt[i].Word)
			//当某行含3个空列时，判断该行为空，将参考区间内容拼接到上一行
			if  Rkey==0 && opt[i+1].Word=="" && opt[i+2].Word=="" && opt[i+3].Word==""{
				tempstr:=opt[i+5].Word
				x,err:=strconv.Atoi(detail["序"])
				if err!=nil{
					panic(err)
				}
				AddTarget:=alldetail[x]["参考区间"]
				alldetail[x]["参考区间"]=AddTarget+","+tempstr
				i+=column
				continue
			}
			//按引用传递会将detail与外层alldetail绑定，导致修改时产生联动，故创建一个新的detail用于接收新的一行的数据
			detail = make(map[string]string)
		}
		//内层map的key，
		Lkey := str[2]
		if Lkey=="较平均" {
			switch opt[i].Word {
			case "↑":
				detail[Lkey] ="偏高"
				alldetail[Rkey] = detail
				i+=1
				continue
			case "":
				detail[Lkey] ="正常"
				alldetail[Rkey] = detail
				i+=1
				continue
			case "↓":
				detail[Lkey] ="偏低"
				alldetail[Rkey] = detail
				i+=1
				continue
			}
		}
		//向map中填充每行数据，即(序：Value，代号:Value,参考：Value...)
		detail[Lkey] = opt[i].Word
		//存整行数据
		alldetail[Rkey] = detail
		i++
	}
	return alldetail
}
//Two table in the received data and the column is 6(Can been selected)
func Tab2Column6(tempURL string,tempID string,param string,column int) map[int]map[string]string{
	temparam:=fmt.Sprintf("templateSign=%s&%s",tempID,param)
	//temparam := "templateSign=c373b9864410930753ad0524c7b8cd82&"+param
	resp, err := http.Post(tempURL, "application/x-www-form-urlencoded", strings.NewReader(temparam))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	var alldata Alldata
	//反序列化
	err1:= json.Unmarshal(body, &alldata)
	if err1!= nil {
		fmt.Println(err)
	}
	//detail存每行详细数据
	var detail = make(map[string]string)
	//alldetail存 行号+detail
	var  alldetail = make(map[int]map[string]string)
	//取得所有百度返回数据
	var opt = alldata.Data.Ret
	//循环变量
	var i =0
	//行号
	var Rkey int
	for  i < len(opt){
		str:=strings.Split(opt[i].Wordname,"#")
		//当循环次数到达新的一行时，i%7=0
		if i%column == 0{
			Rkey+=1
			//按引用传递会将detail与外层alldetail绑定，导致修改时产生联动，故创建一个新的detail用于接收新的一行的数据
			detail = make(map[string]string)
		}
		//内层map的key，
		//Lkey := opt[i].Wordname[36:]
		Lkey := str[2]
		if Lkey=="较平均" {
			switch opt[i].Word {
			case "↑":
				detail[Lkey] ="偏高"
				alldetail[Rkey] = detail
				i+=1
				continue
			case "":
				detail[Lkey] ="正常"
				alldetail[Rkey] = detail
				i+=1
				continue
			case "↓":
				detail[Lkey] ="偏低"
				alldetail[Rkey] = detail
				i+=1
				continue
			}
		}
		//向map中填充每行数据，即(序：Value，代号:Value,参考：Value...)
		detail[Lkey] = opt[i].Word
		//存整行数据
		alldetail[Rkey] = detail
		i++
	}
	return alldetail
}
//check err
func check(err error){
	if err!=nil{
		panic(err)
	}
}
//Cover privacy message
//使用类型断言
type Img interface {
	Set(x, y int, c color.Color)
}
func ModifyPixel(src gocv.Mat,x Result){
	var Top,Left,Height,Width uint
	if src.Empty(){
		fmt.Println("读取图片失败")
	}
	// 转化成img
	img, _ := src.ToImage()
	if 1==(*gocv.Mat).Channels(&src){
		gocv.CvtColor(src,&src,gocv.ColorGrayToBGR)
	}
	for _,v:=range x.Wr {
		if strings.Index(v.Words,"姓名")==-1{
			continue
		}else {
			Top=v.Location.Top
			Left=v.Location.Left
			Height=v.Location.Height
			Width=v.Location.Width
			break
		}
	}
	for i :=Top; i <= Top+Height; i++ {
		for j := Left; j <= Left+Width; j++ {
			img.(Img).Set(int(j), int(i), color.RGBA{0, 0, 0, 255})
		}
	}
	dst,err:=gocv.ImageToMatRGBA(img)
	if err!=nil{
		fmt.Println(err)
	}
	window:=gocv.NewWindow("CoverMessage")
	window.IMShow(dst)
	window.WaitKey(0)
}


//correct img
func getCorrect(fileurl string) gocv.Mat{
	//read
	graycycle:=gocv.IMRead(fileurl,gocv.IMReadColor)
	if graycycle.Empty(){
		fmt.Println("读取图片失败")
	}
	//Warning:Garbled
	window:= gocv.NewWindow("Original Image")
	window.IMShow(graycycle)
	window.WaitKey(1000)
	//get Spin angle
	angle:=CalcDegree(graycycle)
	dst := gocv.NewMat()
	//Change Original image
	FinImg:=rotateImg(graycycle,&dst,angle)

	//_ need to change
	_, err := FinImg.ToImage()
	check(err)

	return FinImg
}
//calculate Degree
func CalcDegree(src gocv.Mat,) float64{
	var best_angle float64
	var max_cnt int
	total:=make(map[float64]int)
	//var midImage,dstImage gocv.Mat   ||Error：notwork
	midImage := gocv.NewMat()
	dstImage := gocv.NewMat()
	//Canny finds edges in an image using the Canny algorithm.
	//The smallest value between threshold1 and threshold2 is used for edge linking
	gocv.Canny(src,&midImage,50,200)
	//It converts the src Mat image to the dst Mat using the
	//code param containing the desired ColorConversionCode color space.
	gocv.CvtColor(midImage,&dstImage,gocv.ColorGrayToBGR)
	lines:=gocv.NewMat()
	//HoughLinesP implements the probabilistic Hough transform
	//algorithm for line detection(Hough变换检测直线，threshold为阈值，越高干扰愈低)
	gocv.HoughLinesPWithParams(midImage,&lines,1,math.Pi/180,300,80,200)
	for i := 0; i < lines.Rows(); i++ {
		//x,y Point in Image,Two Point can defined a straight line
		pt1 := image.Pt(int(lines.GetVeciAt(i, 0)[0]), int(lines.GetVeciAt(i, 0)[1]))
		pt2 := image.Pt(int(lines.GetVeciAt(i, 0)[2]), int(lines.GetVeciAt(i, 0)[3]))
		//draw Line in Image(color)
		gocv.Line(&dstImage, pt1, pt2, color.RGBA{0, 255, 0, 50}, 10)
		//angle=arctan(y/x)
		ang_:=math.Atan2(float64(lines.GetVeciAt(i, 0)[3])-float64(lines.GetVeciAt(i, 0)[1]),float64(lines.GetVeciAt(i, 0)[2])-float64(lines.GetVeciAt(i, 0)[0]))
		//transfer radian(弧度) to angle
		theta:=math.Pi/2 + ang_
		fingle:=theta/math.Pi*360
		fingle=math.Round(180-fingle)
		//???
		fingle=-1*fingle/2
		//Use HashMap storage most data
		_,ok:=total[fingle]
		if ok{
			total[fingle]++
			if total[fingle]>max_cnt {
				max_cnt = total[fingle]
				best_angle = fingle
			}
		}else{
			total[fingle] = 1
		}
	}
	return best_angle
}
//revolve Img
func rotateImg(src gocv.Mat,dst *gocv.Mat,angle float64) gocv.Mat{
	length:=math.Sqrt(float64(src.Rows()*src.Rows()+src.Cols()+src.Cols()))

	//pt3:=image.Point{X: (src.Rows()-350)/2,Y: (src.Cols()-250)/2}
	pt3:=image.Point{X: (src.Rows()-350)/2,Y: (src.Cols()-250)/2}
	sz:=image.Point{X: int(length*1.6),Y: int(length)}
	//sz:=image.Point{X: int(length),Y: int(length)}

	M:=gocv.GetRotationMatrix2D(pt3,angle,1)
	gocv.WarpAffine(src,dst,M,sz)
	return *dst
}

